<script setup lang="ts">
import {
  addRegionCustomRoute,
  CustomRoutePose,
  deleteRegionCustomRoute, deleteRegionOtherRoute,
  editRegionCustomRoute,
  editTaskRoute, getEightDirPose,
  getRollPitch,
  getSogCog,
  RegionCustomRouteData,
  RouteAttitude,
  RoutePoint,
  TaskItem
} from '@/services/task';
import {formatLon, getLonLat, toLonLatString} from 'common/src/utils/lnglat';
import {routeOptions, taskOptions, TaskType} from 'common/src/utils/special';
import {ElButton, ElIcon, ElInput, ElMessage, ElMessageBox, ElScrollbar, ElTable, ElTooltip} from 'element-plus';
import {ArrowLeftBold, CloseBold} from '@element-plus/icons-vue';
import echarts from 'common/src/utils/echarts';
import dayjs from 'dayjs';
import {calcAzimuth, calcDistanceNM} from 'common/src/utils/routeCalc';
import {getCustomLocalTimeFormat, getTimestampFromCustomFormatZ} from 'common/src/utils/time';
import {useTaskStore, useTimeRangeStore, useWeatherStore, useUserStore} from '@/store';
import {GeoJSONFeature, MapMouseEvent, Marker} from 'mapbox-gl';
import {
  addRoutePointMarker,
  changeAttitudeColor,
  getEmptyGeoJson,
  hideRegionRoute,
  hideRouteAttitudeSrc,
  shipAttitudeLayerId,
  updateEditRouteSrc,
  updateRouteAttitudeSrc,
  updateTaskRegionSrc
} from 'common/src/utils/layer';
import {getAttitudeColor} from 'common/src/utils/color';
import AddRouteSvg from 'common/src/assets/imgs/add-route.svg?component';
import LRArrowSvg from 'common/src/assets/imgs/lrArrow.svg?component';
import TaskCard from './TaskCard.vue';
import FormMarker from "@/pages/home/topLeft/FormMarker.vue";
import DataCompare from "@/pages/home/topLeft/DataCompare.vue";
import {customRouteSpdOptions, splitLineColor} from 'common/src/utils/special';
import SafetyJudge from './SafetyJudge.vue';
import {getKtsFromMPerSec} from 'common/src/utils/weather';

interface Props {
  visible: boolean;
  task?: TaskItem;
  poses: Record<number, RouteAttitude[]>;
  posesLoading: boolean;
  customRoutePose: CustomRoutePose[];
  customLoading: boolean;
}

interface Emits {
  (e: 'update:visible', visible: boolean): void;

  (e: 'return'): void;

  (e: 'save-route'): void;

  (e: 'refresh-custom-route', id: number): void;

  (e: 'export'): void;
}

interface TableRow extends RoutePoint {
  index: number;
  isPilot: boolean;
  /** 距离下一个点的距离 */
  distance?: number;
  /** 累积距离 */
  totalDis: number;
  isSubPilot?: boolean;
}

// 旗标表单组件对外暴露的方法类型
interface FormMarkerExpose {
  start: () => void;
  cancel: () => void;
  query: () => void;
}

const getInitEditRouteData = (name: string) => {
  return {
    id: -1,
    name,
    sog: 12,
    etd: new Date(),
  };
};

// ================== 单点姿态预测（八方向）相关 ==================
// 八个方向键（与后端约定）
type DirKey = 'N' | 'NE' | 'E' | 'SE' | 'S' | 'SW' | 'W' | 'NW';

// 下拉选项（展示中文，value 为后端方向键）
const DIR_OPTIONS: Array<{ label: string; value: DirKey }> = [
  {label: '正北', value: 'N'},
  {label: '东北', value: 'NE'},
  {label: '正东', value: 'E'},
  {label: '东南', value: 'SE'},
  {label: '正南', value: 'S'},
  {label: '西南', value: 'SW'},
  {label: '正西', value: 'W'},
  {label: '西北', value: 'NW'},
];

// 后端返回结构：每个方向一组 RouteAttitude[]
interface FlagForecastResp {
  N: RouteAttitude[];
  NE: RouteAttitude[];
  E: RouteAttitude[];
  SE: RouteAttitude[];
  S: RouteAttitude[];
  SW: RouteAttitude[];
  W: RouteAttitude[];
  NW: RouteAttitude[];
}

// 单点姿态预测状态容器（与原有逻辑解耦）
const flagForecast = reactive({
  active: false,                    // 是否处于单点姿态模式
  loading: false,                   // 加载态
  selected: 'N' as DirKey,          // 当前选中方向（默认正北）
  curves: {} as Partial<FlagForecastResp>, // 八方向曲线缓存
});

// —— 转向峰值角标配置 ——
// 角标阈值（绝对值大于等于该角度才显示角标）
const TURN_BADGE_THRESHOLD = 12; // 单位：度，可按需调整
// 相邻峰值最小间隔（以采样点为单位，防止一段小抖动产生多个角标）
const TURN_PEAK_MIN_GAP = 6; // 建议 4~10 之间
// —— markLine 统一样式配置 ——
const MARKLINE_COLOR = '#ffffff'; // 统一的竖线颜色（上下两张图公用）

/**
 * 打开/更新“单点姿态预测”面板：
 * 仅渲染曲线，不改动地图图层与监听（与原 handleAttitudeManage 解耦）
 */
const openFlagForecastPanel = (route: RouteAttitude[]) => {
  showAttitude.value = true;
  showPoint.value = false;
  showDeviation.value = false;
  showSafetyJudge.value = false;
  // 延迟到下一帧再初始化/更新图表，避免容器尚未完成布局导致宽度为 0
  setTimeout(() => {
    updateLineChart(route || []);
    // 同步在地图上绘制该方向的航线（仅 LineString）
    drawFlagForecastLine(route || []);
    // 首次 init 或切换后强制自适应一次，修复 canvas 0 宽问题
    try {
      (lineChart as any)?.resize?.();
    } catch {}
  }, 0);
};

/** 下拉方向切换：从缓存中取出对应方向曲线并展示 */
const onFlagDirectionChange = (dir: DirKey) => {
  if (!flagForecast.active) return;
  const route = (flagForecast.curves as Record<DirKey, RouteAttitude[]>)[dir] || [];
  openFlagForecastPanel(route);
};

const compareVisible = ref(false);
// 导航记录对比
const startCompare = ()=> {
  compareVisible.value = true;
}

// 通过 ref 控制子组件
const formMarkerRef = ref<FormMarkerExpose | null>(null)

// 将父组件的 props.task 中的区域信息转换为四点坐标 [lng, lat]
const areaPointsForFlag = computed<Array<[number, number]>>(() => {
  const t = props.task
  if (!t || t.taskLtLongitude == null || t.taskLtLatitude == null || t.taskRbLongitude == null || t.taskRbLatitude == null) {
    return [] // 交由子组件使用默认全局范围
  }
  const lt: [number, number] = [t.taskLtLongitude!, t.taskLtLatitude!]
  const rt: [number, number] = [t.taskRbLongitude!, t.taskLtLatitude!]
  const rb: [number, number] = [t.taskRbLongitude!, t.taskRbLatitude!]
  const lb: [number, number] = [t.taskLtLongitude!, t.taskRbLatitude!]
  return [lt, rt, rb, lb]
})
/** 是否已添加姿态图层监听 */
let attitudeLayerAddedListener = false;
const props = withDefaults(defineProps<Props>(), {});
const emits = defineEmits<Emits>();
const timeRangeStore = useTimeRangeStore();
const weatherStore = useWeatherStore();
const userStore = useUserStore();
const taskStore = useTaskStore();
const showPoint = ref(false);
const showAttitude = ref(false);
const showDeviation = ref(false);
const showSafetyJudge = ref(false);
const taskAttitudeDivRef = ref<HTMLDivElement>();
const taskDeviationDivRef = ref<HTMLDivElement>();
const saveLoading = ref(false);
const tableRef = ref<InstanceType<typeof ElTable>>();
const safetyJudgeRef = ref<InstanceType<typeof SafetyJudge>>();
const editCell = reactive({
  row: -1,
  column: '',
  content: '',
});
const tableData = ref<TableRow[]>([]);
/** 姿态曲线 */
let lineChart: echarts.ECharts | null = null;
/** 对比弹窗使用：当前姿态图（上半部分）的横/纵摇数据 */
const attitudePredictedForCompare = ref<{
  roll: { x: string[]; y: number[] };
  pitch: { x: string[]; y: number[] };
}>({
  roll: { x: [], y: [] },
  pitch: { x: [], y: [] },
});
/** 累积位移曲线 */
let deviationChart: echarts.ECharts | null = null;
const wpMenuContent = ref<HTMLDivElement | null>(null);
const wpListContainer = ref<HTMLDivElement | null>(null);
const clickContextIdx = ref(-1);
/** 另存为加载中 */
const saveAsNewLoading = ref(false);
/** 编辑的航线点数据 */
const markerArr = ref<Marker[]>([]);
/** 编辑的航线或查看的姿态对应的航线类型，如果是-1则为区域自定义 */
const chosenType = ref(1);
/** 选中的自定义航线的信息 */
const editRegionRoute = reactive(getInitEditRouteData(''));
/** 是否已添加区域航线编辑地图监听 */
const editRegionRouteMapListen = ref(false);
// 当前激活的互斥模式：none | 区域编辑 | 旗标单点姿态
const activeMode = ref<'none' | 'regionEdit' | 'flagForecast'>('none');
let editRegionRouteLastClick = [0, 100];

/** 防并发切换：避免一次点击误触发两个模式 */
let _modeSwitching = false;

// —— 修复点击/双击竞争：延迟判定 + 解绑顺序 + 去重 ——
// 单击延迟定时器
let _clickTimer: number | null = null;
// 双击结束保护期：双击发生时，阻断同一组的两次 click
let _finishingByDbl = false;
// 最近一次真正“加点”的时间与坐标（用于兜底去重）
let _lastAddTs = 0;
let _lastAddLng = 999, _lastAddLat = 999;

const _isDuplicatePoint = (lng: number, lat: number) => {
  const dt = Date.now() - _lastAddTs;
  const same =
      Math.abs(lng - _lastAddLng) < 1e-6 &&
      Math.abs(lat - _lastAddLat) < 1e-6;
  // 250ms 内且坐标几乎相同，视为重复点击
  return dt < 250 && same;
};

const routeListMaxHeight = reactive({
  /** 系统航线 */
  system: 0,
  /** 自定义航线 */
  custom: 0,
  /** 姿态图表 */
  attitude: 0,
});

const show = computed({
  get() {
    return props.visible;
  },
  set(visible: boolean) {
    emits("update:visible", visible);
  },
});

const customRoutePoseArr = computed(() => {
  return props.customRoutePose.map(routePose => {
    const {points, pose, name, sog} = routePose;
    const info = getTaskRouteInfo(points, pose);
    return {
      name,
      sog,
      ...info,
    };
  });
});

const taskInfo = computed(() => {
  if (props.task) {
    const {
      name,
      taskStage,
      ptdUtc,
      points,
      routeType,
      routes,
      sog,
      taskLtLatitude,
      taskLtLongitude,
      taskRbLatitude,
      taskRbLongitude,
    } = props.task;
    const depPoint = points[0];
    const desPoint = points[points.length - 1];
    const dep = depPoint?.id ? depPoint.name! : toLonLatString(depPoint.longitude, depPoint.latitude);
    const des = desPoint?.id ? desPoint.name! : toLonLatString(desPoint.longitude, desPoint.latitude);
    const task = taskOptions.find(opt => opt.value === taskStage);
    let lTLngLat = '';
    let rBLngLat = '';
    if (taskStage === TaskType.warStart) {
      lTLngLat = toLonLatString(taskLtLongitude!, taskLtLatitude!);
      rBLngLat = toLonLatString(taskRbLongitude!, taskRbLatitude!);
    }
    return {
      name: name,
      eta: dayjs(getTimestampFromCustomFormatZ(ptdUtc, 'YYYY-MM-DD HH:mm:ss')).format('YYYY/MM/DD HH:mm:ss'),
      route: `${dep} - ${des}`,
      stage: task?.label,
      type: routeType,
      sog,
      lTLngLat,
      rBLngLat,
      typeInfo: routeType.map(type => {
        const points = routes[type] || [];
        const poses = props.poses[type];
        return getTaskRouteInfo(points, poses);
      }),
    };
  }
  return {};
});

const pointTableHeight = computed(() => {
  let minusPx = 95 + 180 + 94 + 24 + 35 + 48;
  if (chosenType.value === -1) {
    minusPx += 30;
  }
  if (props.task?.taskStage === TaskType.warStart) {
    minusPx += 62;
  }
  return `calc(100vh - ${minusPx}px)`;
});

const clearEditState = () => {
  // @ts-ignore
  markerArr.value.map((marker) => (marker as any).remove());
  markerArr.value = [];
  const geoJson = getEmptyGeoJson();
  updateEditRouteSrc(weatherStore.map as any, geoJson);
  hideRouteAttitudeSrc(weatherStore.map as any);
  if (editRegionRouteMapListen.value) {
    editRegionRouteMapListen.value = false;
    weatherStore.map?.off('click', handleRegionRouteAddPoint);
    weatherStore.map?.off('mousemove', handleRegionRouteMovePoint);
    weatherStore.map?.off('dblclick', handleRegionRouteDbClick);
  }
  // 确保恢复地图双击缩放
  weatherStore.map?.doubleClickZoom?.enable?.();
  Object.assign(editRegionRoute, getInitEditRouteData(''));
};

/**
 * 将折线逐段“解包”：让每一段的经度变化都走最短路径（|Δlon| ≤ 180）。
 * 这样 Mapbox 在 renderWorldCopies=true 下渲染为单条 LineString 时，
 * 无论正向还是反向跨越 180° 都不会走“长边”。
 */
function toDirectedPath(coords: Array<[number, number]>) {
  if (!coords || coords.length === 0) return []
  const out: Array<[number, number]> = []
  let prevLon = coords[0][0]
  out.push([prevLon, coords[0][1]])
  for (let i = 1; i < coords.length; i++) {
    const lat = coords[i][1]
    let lon = coords[i][0]
    // 将 lon 平移到最靠近 prevLon 的世界拷贝
    while (lon - prevLon > 180) lon -= 360
    while (prevLon - lon > 180) lon += 360
    out.push([lon, lat])
    prevLon = lon
  }
  return out
}

const updateEditRouteLineLayer = () => {
  const geoJson = getEmptyGeoJson();
  geoJson.features.push({
    type: 'Feature',
    properties: {id: props.task?.id || 0},
    geometry: {
      type: 'LineString',
      coordinates: toDirectedPath(tableData.value.map((point) => [point.lon, point.lat])),
    },
  });
  updateEditRouteSrc(weatherStore.map as any, geoJson);
};

const computeRouteTableContent = (idx: number) => {
  let distanceChange = 0;
  let timeChange = 0;
  if (tableData.value.length < 2) {
    return;
  }
  const startIdx = idx === 0 ? 0 : idx - 1;
  const endIdx = (idx + 1) < (tableData.value.length - 1) ? (idx + 1) : (tableData.value.length - 1);
  for (let i = startIdx; i < endIdx; i++) {
    const currentPoint = tableData.value[i];
    const nextPoint = tableData.value[i + 1];
    const {distance: disNm, sog, ts} = tableData.value[i];
    const distance = calcDistanceNM(
        currentPoint.lon,
        currentPoint.lat,
        nextPoint.lon,
        nextPoint.lat,
        true
    );
    tableData.value[i].totalDis += distanceChange;
    distanceChange += (distance - disNm!);
    tableData.value[i].distance = distance;
    tableData.value[i].cog = Number(
        calcAzimuth(
            currentPoint.lon,
            currentPoint.lat,
            nextPoint.lon,
            nextPoint.lat,
            true
        ).toFixed(0)
    );
    const nextPts = Math.round((distance / sog) * 3600) + ts;
    timeChange = nextPts - tableData.value[i + 1].ts;
    tableData.value[i + 1].ts = nextPts;
  }
  // 更新后续累积距离
  for (let i = idx + 1; i < tableData.value.length; i++) {
    if (tableData.value[i]) {
      tableData.value[i].totalDis += distanceChange;
    }
  }
  // 更新后续时间
  for (let i = idx + 2; i < tableData.value.length; i++) {
    tableData.value[i].ts += timeChange;
  }
};

const updateRouteTable = (ev: { target: Marker; type: 'drag' | 'dragend' }) => {
  taskStore.removeRoutePointMarker();
  const elementId = ev.target.getElement().id.split('-');
  const pointIndex = Number(elementId[1]);
  tableRef.value?.setCurrentRow(tableData.value[pointIndex]);
  // 如果编辑的是区域内自定义航线，需要经纬度受限
  const bounded = chosenType.value === -1;
  const {lng, lat} = bounded ? getPointInRegion(ev.target._lngLat) : ev.target._lngLat;
  tableData.value[pointIndex].lat = lat;
  tableData.value[pointIndex].lon = lng;
  updateEditRouteLineLayer();
  computeRouteTableContent(pointIndex);
  if (ev.type === 'dragend') {
    markerArr.value[pointIndex].setLngLat([lng, lat]);
  }
};

const showPointPopup = (point: TableRow) => {
  const {lon, lat, sog, cog, ts} = point;

  taskStore.updateRoutePointInfo({
    lat, lon,
    latLng: toLonLatString(point.lon, point.lat),
    sog, cog,
    time: getCustomLocalTimeFormat(ts * 1000, 'YYYY-MM-DD HH:mm'),
    roll: undefined,
    pitch: undefined,
    windAngle: undefined, windSpd: undefined, wave: undefined,
  });
  taskStore.updateRoutePointMarker(point.lon, point.lat);
};

const hanleRoutePointClick = (e: MouseEvent) => {
  // 编辑路线时不处理点击事件
  if (editRegionRouteMapListen.value) {
    return;
  }
  e.stopPropagation();
  const elementId = (e.currentTarget as HTMLDivElement)!.id.split('-');
  const pointIndex = Number(elementId[1]);
  tableRef.value?.setCurrentRow(tableData.value[pointIndex]);

  const point = tableData.value[pointIndex];
  showPointPopup(point);
};

const addPointMarker = () => {
  tableData.value.forEach((point, idx) => {
    const id = `routeid-${idx}-wp-marker`;
    const isDraggable = !point.isPilot;
    const marker = addRoutePointMarker(
        weatherStore.map as any,
        point,
        id,
        'wp-marker',
        isDraggable,
        false,
        hanleRoutePointClick
    );
    marker.on('drag', updateRouteTable);
    marker.on('dragend', updateRouteTable);
    markerArr.value.push(marker as any);
  });
};

const handleEditPointChange = () => {
  // @ts-ignore
  markerArr.value.map((marker) => (marker as any).remove());
  markerArr.value = [];
  updateEditRouteLineLayer();
  addPointMarker();
};

const getMaxAttitude = (arr: RouteAttitude[]) => {
  let maxRoll = 0;
  let maxPitch = 0;
  arr.forEach(point => {
    const roll = Math.abs(point.roll_pred);
    const pitch = Math.abs(point.pitch_pred);
    if (roll > maxRoll) {
      maxRoll = roll;
    }
    if (pitch > maxPitch) {
      maxPitch = pitch;
    }
  });
  return {
    roll: Number(maxRoll.toFixed(2)),
    pitch: Number(maxPitch.toFixed(2)),
  };
};

const getTaskRouteInfo = (route: { ts: number; lon: number; lat: number }[], attitudes?: RouteAttitude[]) => {
  if (!route?.length) {
    return {
      eta: 0,
      distance: 0,
      spd: 0,
    };
  }
  const firstPoint = route[0];
  const lastPoint = route[route.length - 1];
  let distance = 0;
  route.forEach((point, idx) => {
    if (idx > 0) {
      const last = route[idx - 1];
      distance += calcDistanceNM(last.lon, last.lat, point.lon, point.lat, true);
    }
  });
  // 小时
  const totalTime = (lastPoint.ts - firstPoint.ts) / 60 / 60;
  const rollPitch = getMaxAttitude(attitudes || []);
  return {
    eta: lastPoint.ts * 1000,
    distance: Number(distance.toFixed(2)),
    spd: Number((distance / totalTime).toFixed(2)),
    roll: attitudes === undefined ? -1 : (attitudes.length > 0 ? rollPitch.roll : undefined),
    pitch: attitudes === undefined ? -1 : (attitudes.length > 0 ? rollPitch.pitch : undefined),
  };
};

const handleAttitudeSelectedChange = (event: {
  type: 'legendselectchanged';
  selected: {
    '横摇': boolean;
    '纵摇': boolean;
  };
}) => {
  const rollSelect = event.selected['横摇'];
  const pitchSelect = event.selected['纵摇'];
  if (rollSelect && !pitchSelect) {
    changeAttitudeColor(weatherStore.map as any, true, false);
  } else if (!rollSelect && pitchSelect) {
    changeAttitudeColor(weatherStore.map as any, false, true);
  } else {
    changeAttitudeColor(weatherStore.map as any, false, false);
  }
};

/** 更新姿态曲线 */
const updateLineChart = (route: RouteAttitude[]) => {
  const data = route.map(point => {
    const ts = getTimestampFromCustomFormatZ(point.time, 'YYYY/MM/DD HH:mm:ss');
    const xtime = dayjs(ts).format('MM/DD HH:mm');
    const roll = Number(point.roll_pred.toFixed(2));
    const pitch = Number(point.pitch_pred.toFixed(2));
    return {
      ...point, xtime, roll, pitch, ts,
      cc: point?.course,
      dc: point?.dif_course,
      mwp: point?.mwp,
      waveD: point?.relative_wave_dir,
      speed: point?.speed,
      swh: point?.swh,
      utotal: getKtsFromMPerSec(point?.utotal),
      vtotal: getKtsFromMPerSec(point?.vtotal),
      flow: getKtsFromMPerSec(point?.flow_speed),
      u10: getKtsFromMPerSec(point?.u10),
      v10: getKtsFromMPerSec(point?.v10),
      wind: getKtsFromMPerSec(point?.wind_speed),
    };
  });
  // 记录当前显示的横/纵摇曲线数据（用于 DataCompare 组件）
  attitudePredictedForCompare.value = {
    roll: { x: data.map(d => d.xtime), y: data.map(d => d.roll) },
    pitch: { x: data.map(d => d.xtime), y: data.map(d => d.pitch) },
  };
  const inputFields = [
    {label: '航向', value: 'cc', unit: '°'},
    {label: '转向', value: 'dc', unit: '°'},
    {label: '风速', value: 'wind', unit: 'kts'},
    {label: '西风', value: 'u10', unit: 'kts'},
    {label: '北风', value: 'v10', unit: 'kts'},
    {label: '浪高', value: 'swh', unit: 'm'},
    {label: '波周期', value: 'mwp', unit: 's'},
    {label: '浪夹角', value: 'waveD', unit: '°'},
    {label: '流速', value: 'flow', unit: 'kts'},
    {label: '西流', value: 'utotal', unit: 'kts'},
    {label: '北流', value: 'vtotal', unit: 'kts'},
    // {label: '船速', value: 'speed', unit: 'kts'},
  ];
  // —— 计算转向峰值（用于绘制角标）——
  // dc = 转向（已在 data 中映射为 point.dif_course）
  const dcArr: number[] = data.map(d => (typeof d.dc === 'number' ? d.dc : NaN));
  const turnPeaks: Array<{ x: string; y: number; idx: number }> = [];
  let lastPeakIdx = -9999;
  for (let i = 1; i < dcArr.length - 1; i++) {
    const v = dcArr[i];
    if (!Number.isFinite(v)) continue;
    const av = Math.abs(v);
    if (av < TURN_BADGE_THRESHOLD) continue; // 低于阈值不标注
    const prev = Math.abs(dcArr[i - 1] ?? -Infinity);
    const next = Math.abs(dcArr[i + 1] ?? -Infinity);
    // 局部极大（按绝对值判断）
    if (av >= prev && av >= next) {
      if (i - lastPeakIdx < TURN_PEAK_MIN_GAP) {
        // 与上一个峰太近：保留更“高”的那个
        if (turnPeaks.length && Math.abs(dcArr[lastPeakIdx] ?? -Infinity) < av) {
          turnPeaks.pop();
          turnPeaks.push({ x: data[i].xtime, y: v, idx: i });
          lastPeakIdx = i;
        }
      } else {
        turnPeaks.push({ x: data[i].xtime, y: v, idx: i });
        lastPeakIdx = i;
      }
    }
  }
  // 转成散点所需结构：允许每个点带上自己的 symbolRotate（正▲/负▼）
  const turnPeakData = turnPeaks.map(p => ({ value: [p.x, p.y], symbolRotate: p.y >= 0 ? 180 : 0 }));

  // 找到“转向”所在的副图 y 轴索引（与你现有映射保持一致）
  const dcIndex = inputFields.findIndex(f => f.value === 'dc');
  const dcYAxisIndex = (dcIndex % 2) + 2; // 在第二个 grid（索引1）上的左右轴

  if (!lineChart) {
    const myChart = echarts.init(taskAttitudeDivRef.value! as HTMLDivElement);
    lineChart = myChart;
    lineChart.setOption({
      color: [
        '#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc',
        '#54c036', '#912c15', '#fac838', '#eec666', '#73801e', '#3b8272',
      ],
      grid: [
        {
          left: 40,
          right: 40,
          top: 86,
          bottom: 230,
          // containLabel: true
        },
        {
          left: 40,
          right: 40,
          top: 300,
          bottom: 30,
          // containLabel: true
        },
      ],
      tooltip: {
        trigger: 'axis',
        valueFormatter: (value: number) => {
          if (typeof (value) === 'number') {
            return `${value.toFixed(2)}°`;
          }
          return '--';
        },
      },
      dataZoom: [{
        type: 'inside',
        start: 0,
        end: 100,
        xAxisIndex: [0, 1]
      }],
      dataset: {
        dimensions: ['xtime', 'roll', 'pitch', ...inputFields.map(field => field.value)],
        source: data,
      },
      xAxis: [
        {
          type: 'category',
          nameTextStyle: {
            color: 'rgba(255,255,255,0.45)',
          },
          axisTick: {
            alignWithLabel: true,
          },
          axisLabel: {
            color: 'rgba(255,255,255,0.45)',
          },
        },
        {
          gridIndex: 1,
          type: 'category',
          nameTextStyle: {
            color: 'rgba(255,255,255,0.45)',
          },
          axisTick: {
            alignWithLabel: true,
          },
          axisLabel: {
            color: 'rgba(255,255,255,0.45)',
          },
        },
      ],
      yAxis: [
        {
          type: 'value',
          position: 'left',
          name: '横摇',
          nameTextStyle: {
            color: 'rgba(255,255,255,0.45)',
          },
          axisLabel: {
            color: 'rgba(255,255,255,0.45)',
          },
          splitLine: {
            lineStyle: {
              color: splitLineColor,
            },
          },
        }, {
          type: 'value',
          position: 'right',
          name: '纵摇',
          nameTextStyle: {
            color: 'rgba(255,255,255,0.45)',
          },
          axisLabel: {
            color: 'rgba(255,255,255,0.45)',
          },
          splitLine: {
            lineStyle: {
              color: splitLineColor,
            },
          },
        }, {
          gridIndex: 1,
          type: 'value',
          position: 'left',
          nameTextStyle: {
            color: 'rgba(255,255,255,0.45)',
          },
          axisLabel: {
            color: 'rgba(255,255,255,0.45)',
          },
          splitLine: {
            lineStyle: {
              color: splitLineColor,
            },
          },
        }, {
          gridIndex: 1,
          type: 'value',
          position: 'right',
          nameTextStyle: {
            color: 'rgba(255,255,255,0.45)',
          },
          axisLabel: {
            color: 'rgba(255,255,255,0.45)',
          },
          splitLine: {
            lineStyle: {
              color: splitLineColor,
            },
          },
        },
      ],
      legend: {
        itemWidth: 20,
        data: ['横摇', '纵摇', ...inputFields.map(field => field.label)].map(name => ({
          name,
          textStyle: {
            color: 'rgba(255,255,255,0.45)',
          },
        })),
      },
      series: [
        {
          name: '横摇',
          type: 'line',
          yAxisIndex: 0,
          markLine: {
            data: [],
            animation: false,
            // 顶部竖线：顶部显示箭头
            symbol: ['none', 'arrow'],
            symbolSize: 8,
            lineStyle: { color: MARKLINE_COLOR, width: 1 },
            // 使用最开始的时间显示样式（简洁、仅设置文字颜色与去描边）
            label: {
              show: true,
              textBorderWidth: 0,
              color: 'rgba(255,255,255,0.45)'
            },
          },
        },
        {
          name: '纵摇',
          type: 'line',
          yAxisIndex: 1,
        },
        ...inputFields.map((fields, idx) => ({
          name: fields.label,
          type: 'line',
          xAxisIndex: 1,
          yAxisIndex: idx % 2 + 2,
          tooltip: {
            valueFormatter: (value: number) => {
              if (typeof (value) === 'number' && !Number.isNaN(value)) {
                return `${value.toFixed(2)}${fields.unit}`;
              }
              return '--';
            },
          }
        })),
        // —— 底部坐标系的 markLine（隐藏线，仅用于画竖线/白框）——
        {
          id: '__mark_bottom__',
          name: '__mark_bottom__',
          type: 'line',
          xAxisIndex: 1,
          yAxisIndex: 2, // 底部左轴即可，竖线按 x 轴画与 y 轴无关
          data: [],
          showSymbol: false,
          lineStyle: { opacity: 0 },
          tooltip: { show: false },
          // 底部竖线：在白框位置显示圆点（两端都显示更稳妥）
          markLine: {
            data: [],
            animation: false,
            symbol: ['circle', 'none'],
            lineStyle: { color: MARKLINE_COLOR, width: 1 },
            label: { show: false },
          },
          silent: true,
          z: 5,
        },
        // —— 转向峰值角标（文字框）——
        {
          name: '__turn_badge__',
          type: 'scatter',
          xAxisIndex: 1,
          yAxisIndex: dcYAxisIndex,
          data: turnPeakData,
          symbol: 'circle',
          symbolSize: 4,
          itemStyle: { color: '#FF4D4F' },
          tooltip: { show: false },
          label: {
            show: true,
            position: 'top',
            offset: [0, 2],
            formatter: (p: any) => `{v|转向${Math.round(p.value[1])}°}`,
            backgroundColor: 'transparent',
            borderColor: '#fff',
            borderWidth: 0,
            borderRadius: 4,
            padding: [6, 10],
            rich: { v: { color: '#FF4D4F', fontSize: 14, fontWeight: 700 } },
          },
          z: 100,
          silent: true,
        },
        // —— 转向峰值角标（小三角指示）——
        {
          name: '__turn_badge_tri__',
          type: 'scatter',
          xAxisIndex: 1,
          yAxisIndex: dcYAxisIndex,
          data: turnPeakData,
          symbol: 'triangle',
          // 使用每个数据项自带的 symbolRotate 实现正值▲/负值▼
          symbolSize: 14,
          itemStyle: { color: '#FF4D4F' },
          label: { show: false },
          tooltip: { show: false },
          z: 101,
          silent: true,
        }
      ],
    });
    lineChart.on('legendselectchanged', handleAttitudeSelectedChange as any);
    lineChart.on('mousedown', (event: any) => {
      const time = event.data?.ts;
      if (time !== undefined) {
        timeRangeStore.updateOffset(time - timeRangeStore.start);
      }
    });
  } else {
    lineChart.setOption({
      dataset: { source: data },
      series: [
        { name: '__turn_badge__', data: turnPeakData },
        { name: '__turn_badge_tri__', data: turnPeakData },
        { name: '横摇', markLine: { data: [] } },
        { id: '__mark_bottom__', markLine: { data: [] } },
      ],
    });
  }
};

/** 更新累积位移曲线 */
const updateDeviationChart = (route: RouteAttitude[]) => {
  const data = route.map(point => {
    const {time, totalDrift} = point;
    const ts = getTimestampFromCustomFormatZ(time, 'YYYY/MM/DD HH:mm:ss');
    const xtime = dayjs(ts).format('MM/DD HH:mm');
    const deviation = (totalDrift || (totalDrift === 0)) ? Number(point.totalDrift.toFixed(2)) : undefined;
    return {xtime, deviation};
  });
  if (!deviationChart) {
    deviationChart = echarts.init(taskDeviationDivRef.value! as HTMLDivElement);
    deviationChart.setOption({
      grid: {
        left: 20,
        right: 15,
        top: 16,
        bottom: 10,
        containLabel: true
      },
      tooltip: {
        trigger: 'axis',
        valueFormatter: (value: number) => (value || (value === 0)) ? `${value.toFixed(2)}m` : '--',
      },
      dataZoom: [{
        type: 'inside',
        start: 0,
        end: 100
      }],
      dataset: {
        dimensions: ['xtime', 'deviation'],
        source: data,
      },
      xAxis: {
        type: 'category',
        nameTextStyle: {
          color: 'rgba(255,255,255,0.45)',
        },
        axisTick: {
          alignWithLabel: true,
        },
        axisLabel: {
          color: 'rgba(255,255,255,0.45)',
        },
      },
      yAxis: [
        {
          type: 'value',
          position: 'left',
          // name: '横向累积位移',
          // nameTextStyle: {
          //   color: 'rgba(255,255,255,0.45)',
          // },
          axisLabel: {
            color: 'rgba(255,255,255,0.45)',
          },
          splitLine: {
            lineStyle: {
              color: splitLineColor,
            },
          },
        }
      ],
      series: [
        {
          name: '横向累积位移',
          type: 'line',
          yAxisIndex: 0,
        },
      ],
    });
  } else {
    deviationChart.setOption({
      dataset: {
        source: data,
      },
    });
  }
};

const toTaskList = () => {
  emits('return');
  toRouteList();
  clearEditState();
};

const toRouteList = () => {
  showAttitude.value = false;
  showPoint.value = false;
  showDeviation.value = false;
  showSafetyJudge.value = false;
  // 退出时重置单点姿态模式（彻底与原逻辑解耦）
  flagForecast.active = false;
  flagForecast.curves = {};
  // 统一复位两种互斥模式
  stopFlagForecastMode();
  activeMode.value = 'none';
  clearEditState();
  safetyJudgeRef.value?.clear();
  taskStore.clearCardKeys(); // 清空选中
};

const updateLineChartMarkline = (time: string) => {
  if (!lineChart) return;
  lineChart.setOption({
    series: [
      { name: '横摇', markLine: { data: [{ xAxis: time }] } },
      { id: '__mark_bottom__', markLine: { data: [{ xAxis: time }] } },
    ],
  });
};

/** 姿态信息显示 */
const attitudeInfoShow = (e: MapMouseEvent & {
  features?: GeoJSONFeature[] | undefined;
}) => {
  const {lat, lng} = e.lngLat;
  const properties = e.features![0].properties as any;
  const {pitch_pred, roll_pred, sog, cog, time} = properties;
  const ts = getTimestampFromCustomFormatZ(time, 'YYYY/MM/DD HH:mm:ss');
  const xtime = dayjs(ts).format('YYYY/MM/DD HH:mm:ss');
  const pose = chosenType.value > -1 ? props.poses[chosenType.value] : props.customRoutePose[editRegionRoute.id]?.pose || [];
  const {windAngle, windSpd, wave} = getRollPitch(pose, ts);
  taskStore.updateRoutePointInfo({
    lat, lon: lng, latLng: toLonLatString(lng, lat),
    sog, cog, time: xtime,
    roll: roll_pred, pitch: pitch_pred,
    windAngle, windSpd, wave,
  });
  taskStore.updateRoutePointMarker(lng, lat);
  updateLineChartMarkline(xtime.slice(5, -3));
};

const handleAttitudeManage = (editPoint: RouteAttitude[], routeArr: {
  ts: number;
  sog: number,
  cog: number
}[], type: number) => {
  showAttitude.value = true;
  showPoint.value = false;
  showDeviation.value = false;
  showSafetyJudge.value = false;
  const geoJson = getEmptyGeoJson();
  const convertPoints = toDirectedPath(editPoint.map((point) => [point.lon, point.lat]));
  editPoint.forEach((point, idx) => {
    if (idx < editPoint.length - 1) {
      const curPoint = convertPoints[idx];
      const nextPoint = convertPoints[idx + 1];
      const time = new Date(point.time).valueOf();
      const {sog, cog} = getSogCog(routeArr, time);
      geoJson.features.push({
        type: 'Feature',
        properties: {
          ...point,
          id: props.task?.id || 0,
          type, sog, cog,
          color: getAttitudeColor(Math.max(point.roll_pred, point.pitch_pred)),
          rollColor: getAttitudeColor(point.roll_pred),
          pitchColor: getAttitudeColor(point.pitch_pred),
        },
        geometry: {
          type: 'LineString',
          coordinates: [[curPoint[0], curPoint[1]], [nextPoint[0], nextPoint[1]]],
        },
      });
    }
  });
  updateRouteAttitudeSrc(weatherStore.map as any, geoJson);
  if (!attitudeLayerAddedListener) {
    attitudeLayerAddedListener = true;
    weatherStore.map?.on('mousemove', [shipAttitudeLayerId], attitudeInfoShow);
  }
  setTimeout(() => {
    updateLineChart(editPoint);
  }, 0);
}

const handleShowAttitude = (type: number) => {
  const pose = props.poses[type] || [];
  const route = props.task?.routes[type] || [];
  handleAttitudeManage(pose, route, type);
};

const handleShowCustomAttitude = (idx: number) => {
  chosenType.value = -1;
  const {name, sog, etd} = props.customRoutePose[idx];
  Object.assign(editRegionRoute, {
    id: idx, name, sog,
    etd: new Date(etd),
  });
  const pose = props.customRoutePose[idx]?.pose || [];
  const route = props.customRoutePose[idx]?.points || [];
  handleAttitudeManage(pose, route, -1);
};

const updateTableData = (points: RoutePoint[]) => {
  let lastTotal = 0;
  tableData.value = points.map((point, idx) => {
    const nextPoint = points[idx + 1];
    const nextDistance = nextPoint ? calcDistanceNM(point.lon, point.lat, nextPoint.lon, nextPoint.lat, true) : undefined;
    const curTotal = lastTotal;
    lastTotal += nextDistance || 0;
    return {
      ...point,
      index: idx,
      distance: nextDistance,
      totalDis: curTotal,
      isPilot: idx === 0 || idx === points.length - 1 || point.isMajor,
    };
  });
  handleEditPointChange();
};

const handleShowPoint = (type: number) => {
  showAttitude.value = false;
  showDeviation.value = false;
  showSafetyJudge.value = false;
  showPoint.value = true;
  chosenType.value = type;
  const editPoint: RoutePoint[] = props.task?.routes[type] || [];
  updateTableData(editPoint);
};

const handleShowCustomPoint = (idx: number) => {
  showAttitude.value = false;
  showDeviation.value = false;
  showSafetyJudge.value = false;
  showPoint.value = true;
  chosenType.value = -1;
  const points = props.customRoutePose[idx]?.points || [];
  const {name, sog, etd} = props.customRoutePose[idx];
  Object.assign(editRegionRoute, {
    id: idx, name, sog,
    etd: new Date(etd),
  });
  updateTableData(points.map(point => ({...point, isMajor: false})));
};

const handleDeviationManage = (editPoint: RouteAttitude[]) => {
  showAttitude.value = false;
  showPoint.value = false;
  showSafetyJudge.value = false;
  showDeviation.value = true;
  setTimeout(() => {
    updateDeviationChart(editPoint);
  }, 0);
};

/** 显示横向位移 */
const handleShowDeviation = (type: number) => {
  chosenType.value = type;
  const pose = props.poses[type] || [];
  handleDeviationManage(pose);
};

/** 显示自定义航线横向位移 */
const handleShowCustomDeviation = (idx: number) => {
  chosenType.value = -1;
  const pose = props.customRoutePose[idx]?.pose || [];
  handleDeviationManage(pose);
};

/** 显示安全评估 */
const handleShowSafetyJudge = () => {
  showAttitude.value = false;
  showPoint.value = false;
  showDeviation.value = false;
  showSafetyJudge.value = true;
};

const handleRouteDelete = async (idx: number, flag: string) => {
  if (flag === 'diy') {
    const { name } = props.customRoutePose[idx];
    await ElMessageBox.confirm(`确定删除“${name}”吗？`, '删除航线', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'info',
    });
    try {
      await deleteRegionCustomRoute({ tid: props.task?.id!, index: idx });
      ElMessage({
        type: 'success',
        message: '删除成功',
      });
      emits('refresh-custom-route', props.task?.id!);
    } catch {
      ElMessage({
        type: 'warning',
        message: '删除失败，请重试',
      });
    }
  } else if (flag === 'info') {
    const nonCustomCount = props.task?.routeType?.length || 0;
    if (nonCustomCount <= 1) {
      ElMessage({
        type: 'warning',
        message: '最后一条任务航线不可删除！',
      });
      return;
    }
    let name = routeOptions.find(route => route.value === idx)?.label
    await ElMessageBox.confirm(`确定删除“${name}”吗？`, '删除航线', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'info',
    });
    try {
      await deleteRegionOtherRoute({ tid: props.task?.id!, type: idx });
      ElMessage({
        type: 'success',
        message: '删除成功',
      });
      // 通知父组件刷新任务列表（保证 routeType 变更后 TaskInfo 的 props.task 立即更新）
      emits('save-route');
      emits('refresh-custom-route', props.task?.id!);
    } catch {
      ElMessage({
        type: 'warning',
        message: '删除失败，请重试',
      });
    }
  }
};

const getPointInRegion = (lngLat: { lng: number; lat: number }) => {
  let {lng, lat} = lngLat;
  let {taskLtLongitude, taskLtLatitude, taskRbLongitude, taskRbLatitude} = props.task as Required<TaskItem>;

  // Latitude clamp
  if (lat > taskLtLatitude) {
    lat = taskLtLatitude;
  } else if (lat < taskRbLatitude) {
    lat = taskRbLatitude;
  }

  // Whether the region crosses the anti-meridian
  const crossesAM = taskRbLongitude < taskLtLongitude;

  if (crossesAM) {
    // Work in an unwrapped domain: [LT, RB+360]
    const lt = taskLtLongitude;
    const rb = taskRbLongitude + 360;

    // Bring clicked longitude into the same domain for comparison/clamping.
    // Any longitude on the "west" side (<= RB) gets shifted by +360.
    if (lng <= taskRbLongitude) {
      lng += 360;
    }

    // Clamp to [lt, rb]
    if (lng < lt) {
      lng = lt;
    } else if (lng > rb) {
      lng = rb;
    }

    // IMPORTANT: do not normalize back to [-180,180] here. Keeping the
    // unwrapped value (e.g. 185) preserves continuity across 180° for
    // downstream distance/azimuth and polyline building. Rendering paths will
    // normalize/split as needed.
  } else {
    // Normal case (no crossing)
    if (lng < taskLtLongitude) {
      lng = taskLtLongitude;
    } else if (lng > taskRbLongitude) {
      lng = taskRbLongitude;
    }
    // Normalize to [-180, 180]
    lng = formatLon(lng)[0];
  }

  return {lng, lat};
};

const handleRegionRouteAddPoint = (e: MapMouseEvent) => {
  // 若当前正因双击而结束，忽略这两次 click
  if (_finishingByDbl) return;

  // 取消上一次候选单击
  if (_clickTimer) {
    clearTimeout(_clickTimer);
    _clickTimer = null;
  }

  // 延迟判定：等待是否会发生 dblclick
  _clickTimer = window.setTimeout(() => {
    _clickTimer = null;

    const { lng, lat } = getPointInRegion(e.lngLat);

    // 兜底去重：时间 + 距离阈值
    if (_isDuplicatePoint(lng, lat)) return;
    _lastAddTs = Date.now();
    _lastAddLng = lng;
    _lastAddLat = lat;

    const pointIdx = tableData.value.length - 1;
    tableData.value[pointIdx].lat = lat;
    tableData.value[pointIdx].lon = lng;
    const { totalDis } = tableData.value[pointIdx];
    const { ts, sog } = tableData.value[pointIdx];

    tableData.value.push({
      lat,
      lon: lng,
      isPilot: false,
      index: pointIdx + 1,
      isMajor: false,
      cog: 0,
      ts,
      sog,
      totalDis,
    });
    handleEditPointChange();
  }, 50); // 200~250ms 均可
};

const handleRegionRouteMovePoint = (e: MapMouseEvent) => {
  const {lng, lat} = getPointInRegion(e.lngLat);
  const pointIdx = tableData.value.length - 1;
  tableData.value[pointIdx].lat = lat;
  tableData.value[pointIdx].lon = lng;
  tableRef.value?.setCurrentRow(tableData.value[pointIdx]);
  handleEditPointChange();
  computeRouteTableContent(pointIdx);
};

const handleRegionRouteDbClick = () => {
  // 进入保护期：阻断双击对应的两次 click
  _finishingByDbl = true;
  if (_clickTimer) {
    clearTimeout(_clickTimer);
    _clickTimer = null;
  }

  // 先解绑 click，避免第二下 click 落地
  weatherStore.map?.off('click', handleRegionRouteAddPoint);
  weatherStore.map?.off('mousemove', handleRegionRouteMovePoint);
  weatherStore.map?.off('dblclick', handleRegionRouteDbClick);

  // 结束绘制
  editRegionRouteMapListen.value = false;
  tableData.value.pop();
  handleEditPointChange();
  activeMode.value = 'none';

  // 恢复地图双击缩放
  weatherStore.map?.doubleClickZoom?.enable?.();

  // 保护期稍后解除（确保这一组 click 被忽略）
  window.setTimeout(() => {
    _finishingByDbl = false;
  }, 250);
};

/** 新增区域航线 */
const handleAddRegionRoute = () => {
  if (_modeSwitching) return;
  _modeSwitching = true;
  // 切换到“区域编辑”前：强制关闭“单点姿态”模式，避免双重地图事件
  stopFlagForecastMode();
  activeMode.value = 'regionEdit';

  showAttitude.value = false;
  showDeviation.value = false;
  showSafetyJudge.value = false;
  showPoint.value = true;
  chosenType.value = -1;
  const name = `自定义航线${props.customRoutePose.length + 1}`;

  // ✅ 从任务出航时间取默认 etd（与 taskInfo.eta 一致）
  const depMs = props.task?.ptdUtc
      ? getTimestampFromCustomFormatZ(props.task.ptdUtc, 'YYYY-MM-DD HH:mm:ss')
      : Date.now();
  const defaultEtd = new Date(depMs);
  Object.assign(editRegionRoute, getInitEditRouteData(name, defaultEtd));

  const {etd, sog} = editRegionRoute;
  const ts = Math.floor(etd.valueOf() / 1000);
  tableData.value = [
    {lat: 0, lon: 0, isPilot: false, index: 0, isMajor: false, cog: 0, ts, sog, totalDis: 0},
  ];
  handleEditPointChange();
  if (!editRegionRouteMapListen.value) {
    editRegionRouteMapListen.value = true;
    // 禁用地图双击缩放，避免与绘制双击结束冲突
    weatherStore.map?.doubleClickZoom?.disable?.();
    editRegionRouteMapListen.value = true;
    weatherStore.map?.on('click', handleRegionRouteAddPoint);
    weatherStore.map?.on('mousemove', handleRegionRouteMovePoint);
    weatherStore.map?.on('dblclick', handleRegionRouteDbClick);
  }
  // 解锁：放到下一帧，避免同一次点击触发另一个按钮
  setTimeout(() => { _modeSwitching = false; }, 0);
};

/** 自定义航线速度更新 */
const handleCustomSogChange = () => {
  const {sog} = editRegionRoute;
  tableData.value.forEach((point, idx) => {
    point.sog = sog;
    if (idx > 0) {
      const lastPoint = tableData.value[idx - 1]!;
      const time = Math.floor((lastPoint.distance || 0) / sog * 3600);
      point.ts = lastPoint.ts + time;
    }
  });
};

/** 开始点位时间更新 */
const handleCustomEtdChange = () => {
  const firstTs = tableData.value[0].ts;
  const etd = Math.floor(editRegionRoute.etd.valueOf() / 1000);
  const delta = etd - firstTs;
  tableData.value.forEach(point => point.ts += delta);
};

const handleSaveRegionRoute = async (saveAsNew: boolean) => {
  const {id, sog, etd} = editRegionRoute;
  let name = editRegionRoute.name;
  if (saveAsNew) {
    const {name: oldName} = props.customRoutePose[id];
    saveAsNewLoading.value = true;
    if (name === oldName) {
      name = `${oldName}1`;
    }
  } else {
    saveLoading.value = true;
  }
  try {
    const data: RegionCustomRouteData = {
      tid: props.task?.id!,
      name, sog,
      etd: getCustomLocalTimeFormat(etd, 'YYYY-MM-DD HH:mm:ss'),
      points: tableData.value.map((point) => {
        const {sog, cog, ts, lat, lon} = point;
        return {sog, cog, ts, lat, lon};
      })
    };
    if (id > -1 && !saveAsNew) {
      await editRegionCustomRoute(id, data);
    } else {
      await addRegionCustomRoute(data);
    }
    ElMessage({
      type: 'success',
      message: '保存成功！',
    });
    toRouteList();
    emits('save-route');
  } catch {
    ElMessage({
      type: 'warning',
      message: '保存失败，请重试',
    });
  } finally {
    saveLoading.value = false;
    saveAsNewLoading.value = false;
  }
}

/** 退出单点姿态模式：关闭旗标组件与状态（确保无残留监听/弹窗） */
const stopFlagForecastMode = () => {
  // 无论是否 active，都尝试关闭一次，避免残留 popup/监听
  formMarkerRef.value?.cancel?.();
  // 同步关闭遮罩
  flagForecast.loading = false;
  if (flagForecast.active) {
    flagForecast.active = false;
    flagForecast.curves = {};
    // 如果已展示八方向面板，则一并关闭
    showAttitude.value = false;
    // 清理八方向绘制的线
    clearFlagForecastLine();
  }
};

/** 停止“区域自定义航线编辑”（移除地图监听与拖拽标记） */
const stopRegionRouteEditing = () => {
  if (editRegionRouteMapListen.value || markerArr.value.length) {
    clearEditState(); // 内部已负责移除 marker、解绑 click/mousemove/dblclick
    editRegionRouteMapListen.value = false;
  }
};

/** 使用八方向姿态数据在地图上绘制航线（仅 LineString，不绘制点位） */
const drawFlagForecastLine = (route: RouteAttitude[]) => {
  const geoJson = getEmptyGeoJson();
  if (route && route.length) {
    // 避免跨 180° 走长边：复用组件里已有的 toDirectedPath
    const coords = toDirectedPath(route.map(p => [p.lon, p.lat]));
    geoJson.features.push({
      type: 'Feature',
      properties: { id: props.task?.id || 0, from: 'flag-forecast' },
      geometry: { type: 'LineString', coordinates: coords },
    });
  }
  // 复用“自定义航线”的 source。单点姿态与区域编辑互斥，故不会冲突
  updateEditRouteSrc(weatherStore.map as any, geoJson);
};

/** 清理八方向绘制的航线（清空 LineString） */
const clearFlagForecastLine = () => {
  const empty = getEmptyGeoJson();
  updateEditRouteSrc(weatherStore.map as any, empty);
};

/** 用户点击旗标弹层“取消/关闭” */
const handleFlagCancel = () => {
  // 关闭遮罩，清理单点模式与面板
  flagForecast.loading = false;
  if (flagForecast.active) {
    showAttitude.value = false; // 若已展开八方向面板，则关闭
  }
  flagForecast.active = false;
  flagForecast.curves = {};
  activeMode.value = 'none';
  clearFlagForecastLine();
};

/** 单点姿态预测：启动旗标拾取（与“区域编辑”互斥） */
const handleAddPoseForecast = () => {
  if (_modeSwitching) return;
  _modeSwitching = true;
  // 无论是否处于区域编辑模式，都先尝试清理自定义航线的图层/标记/监听
  // 解决：结束编辑后 activeMode 可能为 'none'，但地图上仍保留编辑线的问题
  stopRegionRouteEditing();

  // 关闭其它面板，避免交互混淆
  showPoint.value = false;
  showAttitude.value = false;
  showDeviation.value = false;
  showSafetyJudge.value = false;

  // 进入旗标模式
  activeMode.value = 'flagForecast';
  formMarkerRef.value?.start?.();
  // 解锁：放到下一帧，避免同一次点击触发另一个按钮
  setTimeout(() => { _modeSwitching = false; }, 0);
};

// 接收子组件确认事件（可按需接入后端查询逻辑）
const handleFlagConfirm = async (payload: {
  startTime: string; speed: number; duration: number; lng: number; lat: number
}) => {
  // 进入单点姿态模式（与原路线姿态模式隔离）
  flagForecast.active = true;
  flagForecast.loading = true;
  flagForecast.selected = 'N'; // 默认正北

  try {
    // 请求八方向曲线
    const resp: FlagForecastResp = await getEightDirPose({
      ts: dayjs(payload.startTime).valueOf()/1000,
      sog: payload.speed,
      hours: payload.duration,
      lon: payload.lng,
      lat: payload.lat,
    });
    // 缓存并展示默认方向（正北）
    flagForecast.curves = resp;
    openFlagForecastPanel(resp?.N || []);
  } catch (err) {
    ElMessage.warning('获取单点姿态预测失败，请重试');
  } finally {
    flagForecast.loading = false;
  }
};

const closeTaskInfo = () => {
  show.value = false;
  toRouteList();
  timeRangeStore.refreshTimeRange();
  taskStore.showTaskRoutePoint();
  updateTaskRegionSrc(weatherStore.map as any);
  hideRegionRoute(weatherStore.map as any);
  clearEditState();
};

const handleSavePoint = async (saveAsNew = false) => {
  if (chosenType.value === -1) {
    // 是区域自定义航线，走区域航线逻辑
    handleSaveRegionRoute(saveAsNew);
    return;
  }
  saveLoading.value = true;
  try {
    await editTaskRoute({
      tid: props.task?.id!,
      type: chosenType.value,
      points: tableData.value.map(point => {
        const {lat, lon, cog, sog, ts, isPilot} = point;
        return {lat, lon, cog, sog, ts, isMajor: isPilot};
      }),
    });
    ElMessage({
      type: 'success',
      message: '保存成功！',
    });
    emits('save-route');
  } catch {
    ElMessage({
      type: 'warning',
      message: '保存失败，请重试',
    });
  } finally {
    saveLoading.value = false;
  }
};

const closeMenu = () => {
  const menu = wpMenuContent.value;
  if (menu) {
    menu.style.display = 'none';
    wpListContainer.value?.removeEventListener('click', closeMenu);
  }
};

// —— 经纬度输入限制与正则校验 ——
// 仅允许的字符：数字、空格、点、度(°)、分(′)、逗号、以及 NESW（只允许大写）
const _ALLOWED_INPUT_RE = /[^0-9\s\.\u00B0\u2032,NEWS]/g; // \u00B0=°  \u2032=′
// 期望格式：LAT° MIN′ [N|S],LON° MIN′ [E|W]  —— 例："22° 4.75′ N,113° 51.27′ E"
const _DMS_RE = /^\s*(\d{1,2})°\s*(\d+(?:\.\d+)?)′\s*([NS])\s*,\s*(\d{1,3})°\s*(\d+(?:\.\d+)?)′\s*([EW])\s*$/;

/**
 * 输入中即时清洗：
 *  - 强制大写 NESW
 *  - 将 ' 替换为正式分符号 ′
 *  - 过滤掉不允许的字符
 */
const handleLocationInput = (e: Event) => {
  const el = e.target as HTMLInputElement;
  let v = (el.value || '').toUpperCase();
  v = v.replace(/'/g, '′');
  v = v.replace(_ALLOWED_INPUT_RE, '');
  // 将度符统一为 °，避免用户粘贴奇怪字符（可选）
  v = v.replace(/[º˚]/g, '°');
  // 规范逗号两侧空格
  v = v.replace(/\s*,\s*/g, ',');
  el.value = v;
  // 同步到双向绑定内容
  if (editCell && typeof editCell.content === 'string') editCell.content = v;
};

/** 解析形如 "22° 4.75′ N,113° 51.27′ E" 为 { lat, lon }（十进制度） */
function parseDMSPairToDecimal(input: string): { lat: number; lon: number } {
  const s = input.trim().toUpperCase().replace(/'/g, '′');
  const m = _DMS_RE.exec(s);
  if (!m) {
    throw new Error('格式不正确');
  }
  const latDeg = Number(m[1]);
  const latMin = Number(m[2]);
  const latHem = m[3] as 'N' | 'S';
  const lonDeg = Number(m[4]);
  const lonMin = Number(m[5]);
  const lonHem = m[6] as 'E' | 'W';

  // 基本范围校验（分 < 60；90°与180°时分必须为0）
  if (!(latDeg >= 0 && latDeg <= 90)) throw new Error('纬度越界');
  if (!(lonDeg >= 0 && lonDeg <= 180)) throw new Error('经度越界');
  if (!(latMin >= 0 && latMin < 60)) throw new Error('纬度越界');
  if (!(lonMin >= 0 && lonMin < 60)) throw new Error('经度越界');
  if (latDeg === 90 && latMin > 0) throw new Error('纬度 90° 仅允许 0′');
  if (lonDeg === 180 && lonMin > 0) throw new Error('经度 180° 仅允许 0′');

  // 转十进制
  let lat = latDeg + latMin / 60;
  if (latHem === 'S') lat = -lat;
  let lon = lonDeg + lonMin / 60;
  if (lonHem === 'W') lon = -lon;

  // 最终范围（含负号）
  if (lat < -90 || lat > 90) throw new Error('纬度超出 [-90,90]');
  if (lon < -180 || lon > 180) throw new Error('经度超出 [-180,180]');

  return { lat, lon };
}

const handleEditLocationBlur = () => {
  const { row, content } = editCell;
  if (row < 0) return;

  // 1) 严格按指定格式校验并解析
  let lat = -100, lon = -200;
  try {
    const { lat: _lat, lon: _lon } = parseDMSPairToDecimal(content);
    lat = _lat;
    lon = _lon;
  } catch (err: any) {
    ElMessage({ type: 'warning', message: `请输入如 “22° 4.75′ N,113° 151.27′ E” 的坐标（仅支持 N/E/S/W）：${err?.message || ''},修改失败，请重试` });
    return;
  }

  // 2) 区域内编辑时，套用边界限制（含跨反日界线情况）
  const bounded = chosenType.value === -1;
  if (bounded) {
    const lnglat = getPointInRegion({ lng: lon, lat });
    lat = lnglat.lat;
    lon = lnglat.lng; // 注意：可能在跨日界线场景被展开到 >180 的临时域
  }

  // 3) 写入并联动更新
  tableData.value[row].lat = lat;
  tableData.value[row].lon = lon;
  updateEditRouteLineLayer();
  markerArr.value[row].setLngLat([lon, lat]);
  computeRouteTableContent(row);
  editCell.row = -1;
};

const handleCellDblClick = (row: TableRow, column: any) => {
  const property = column.property
  if (property === 'location') {
    if (row.isPilot || row.isSubPilot) {
      return;
    }
    editCell.content = toLonLatString(row.lon, row.lat);
  } else {
    return;
  }
  editCell.row = row.index;
  editCell.column = property;
};

const handleRowClick = (row: TableRow) => {
  showPointPopup(row);
};

const handleRowRightClick = (row: TableRow, _column: any, event: MouseEvent) => {
  event.preventDefault();
  const menu = wpMenuContent.value;
  if (!menu || row.index === 0) return;

  menu.style.display = 'block';
  const clientY = event.clientY;
  const clientX = event.clientX;
  // const cha_y = document.body.clientHeight - event.clientY;
  tableRef.value?.setCurrentRow(row);
  clickContextIdx.value = row.index;
  menu.style.left = clientX - 10 + 'px';
  menu.style.top = clientY - 95 + 'px';
  wpListContainer.value?.addEventListener('click', closeMenu);
};

const pushRowUp = () => {
  const index = clickContextIdx.value;
  const lastPoint = tableData.value[index - 1];
  const currentPoint = tableData.value[index];
  const insertPoint = {...currentPoint};
  // 手动插入的点：不作为首尾/关键点（不带 *，允许删除/拖拽）
  insertPoint.isPilot = false;
  insertPoint.isSubPilot = false;
  insertPoint.isMajor = false;
  insertPoint.lat = (lastPoint.lat + currentPoint.lat) / 2;
  insertPoint.lon = (lastPoint.lon + currentPoint.lon) / 2;
  const distance1 = calcDistanceNM(
      lastPoint.lon,
      lastPoint.lat,
      insertPoint.lon,
      insertPoint.lat,
      true,
  );
  const distance2 = calcDistanceNM(
      insertPoint.lon,
      insertPoint.lat,
      currentPoint.lon,
      currentPoint.lat,
      true,
  );
  tableData.value[index - 1].distance = distance1;
  insertPoint.distance = distance2;
  insertPoint.totalDis = insertPoint.totalDis - distance2;
  insertPoint.cog = lastPoint.cog;
  // 校正 index，避免继承当前点的 index
  insertPoint.index = index;
  tableData.value.splice(index, 0, insertPoint);
  // 重新为后续点位编号，保持 index 连续正确
  for (let i = index + 1; i < tableData.value.length; i++) {
    tableData.value[i].index = i;
  }
  handleEditPointChange();
};

const deleteRow = () => {
  const index = clickContextIdx.value;
  const deleteDistance = tableData.value.find((item) => item.index === index)?.distance || 0;
  tableData.value = tableData.value.filter((item) => item.index !== index);
  const lastPoint = tableData.value[index - 1];
  const currentPoint = tableData.value[index];
  const newDistance = calcDistanceNM(
      lastPoint.lon,
      lastPoint.lat,
      currentPoint.lon,
      currentPoint.lat,
      true,
  );
  const newCog = Number(calcAzimuth(
      lastPoint.lon,
      lastPoint.lat,
      currentPoint.lon,
      currentPoint.lat,
      true,
  ).toFixed(0));
  tableData.value[index - 1].cog = newCog;
  const distanceChange = newDistance - ((tableData.value[index - 1].distance || 0) + deleteDistance);
  tableData.value[index - 1].distance = newDistance;
  const currentPts = Math.round((newDistance / lastPoint.sog) * 3600) + lastPoint.ts;
  const timeChange = currentPts - currentPoint.ts;
  // 更新当前点及后续时间
  for (let i = index; i < tableData.value.length; i++) {
    tableData.value[i].index = tableData.value[i].index - 1;
    tableData.value[i].ts += timeChange;
    tableData.value[i].totalDis += distanceChange;
  }
  tableRef.value?.setCurrentRow(null);
  handleEditPointChange();
};

const handleResize = () => {
  // 减去顶部
  let total = document.documentElement.clientHeight - 95 - 174 - 10;
  if (props.task?.taskStage === TaskType.warStart) {
    total = total - 62;
  }
  // 减去底部
  total = total - 50 - 24 - 48 - 10;
  const attitude = total - 33;
  let system = total, custom = 0;
  if (props.customRoutePose.length > 0) {
    // -24是每个大卡片上下padding
    const systemTotal = (props.task?.routeType.length || 0) * 120 + 18;
    const customTotal = props.customRoutePose.length * 120 + 18;
    if (systemTotal + customTotal > total) {
      const halfTotal = total / 2;
      if (systemTotal > halfTotal && customTotal > halfTotal) {
        system = halfTotal;
        custom = halfTotal;
      } else if (systemTotal > halfTotal) {
        custom = customTotal;
        system = total - customTotal - 12;
      } else {
        system = systemTotal;
        custom = total - systemTotal - 12;
      }
    } else {
      // 高度足够放下所有，最大高度就无所谓多大了，+100留点冗余
      system = systemTotal + 100;
      custom = customTotal + 100;
    }
  }
  routeListMaxHeight.system = system - 24;
  routeListMaxHeight.custom = custom - 24;
  routeListMaxHeight.attitude = attitude;
};

// 放在 <script setup> 里，紧跟 useTaskStore 之后
const isActive = (key: number) => taskStore.isCardActive?.(key) ?? taskStore.activeCardKeys.has(key)
const toggleKey = (key: number) => taskStore.toggleCardKey(key)


/** 外层点击：仅当不是点击功能按钮区时，才切换选中 */
const onTaskCardClick = (e: MouseEvent, key: number) => {
  const target = e.target as HTMLElement;
  if (
      target.closest('.task-route-item-btns') || // 三个功能按钮容器
      target.closest('.task-route-item-delete')  // 删除图标容器（自定义卡片）
  ) {
    return; // 不改变选中态，保证原有功能点击逻辑不受影响
  }
  toggleKey(key);
};
watch(show, (val) => {
  if (val) {
    setTimeout(() => {
      wpListContainer.value = document.querySelector('.el-dialog.custom-dialog.task-info-dialog .el-dialog__body');
    }, 0);
  }
});

watch(() => props.customRoutePose.length, () => {
  handleResize();
});

// 监听 userStore.taskPhase 的变化，根据需求关闭区划
watch(() => userStore.taskPhase, (newPhase) => {
  toTaskList()
});

onMounted(() => {
  handleResize();
  window.addEventListener('resize', handleResize);
});

onBeforeUnmount(() => {
  if (wpMenuContent.value) {
    wpMenuContent.value.style.display = 'none';
  }
  wpListContainer.value?.removeEventListener('click', closeMenu);
  window.removeEventListener('resize', handleResize);
});

defineExpose({
  clearEditState,
  toRouteList,
});
</script>

<template>
  <ElDialog
      :class="['custom-dialog', 'task-info-dialog']"
      v-model="show"
      title=""
      width="420"
      :modal="false"
      modal-class="pointer-none-dialog-modal"
      :close-on-click-modal="false"
      :align-center="false"
  >
    <div class="task-info-card">
      <div class="task-info-header">
        <ElIcon class="task-info-return" @click="() => toTaskList()">
          <ArrowLeftBold/>
        </ElIcon>
        <span class="task-info-name">任务编号：{{ taskInfo.name }}</span>
        <ElIcon class="task-info-close" @click="() => closeTaskInfo()">
          <CloseBold/>
        </ElIcon>
      </div>
      <div class="task-info-line task-info-stage">
        <span>任务阶段：{{ taskInfo.stage }}</span>
        <ElButton size="small" type="primary" @click="() => emits('export')">导出
        </ElButton>
      </div>
      <div class="task-info-line">起终点：{{ taskInfo.route }}</div>
      <div v-if="props.task?.taskStage === TaskType.warStart" class="task-info-line task-info-region">
        <span>任务区域：</span>
        <span class="task-info-region-loc">
          <span>{{ taskInfo.lTLngLat }}</span>
          <span>{{ taskInfo.rBLngLat }}</span>
        </span>
        <div style="display: flex;flex-flow: column nowrap;justify-content: space-between">
          <div
            class="task-action-btn"
            :class="{ 'is-active': activeMode === 'regionEdit' }"
            @click.stop="() => handleAddRegionRoute()"
          >
            <AddRouteSvg class="add-route-icon"/>
            <span>添加自定义航线</span>
          </div>
          <div
            class="task-action-btn"
            :class="{ 'is-active': activeMode === 'flagForecast' }"
            @click.stop="() => handleAddPoseForecast()"
          >
            <LRArrowSvg class="add-route-icon"/>
            <span>八方向姿态预测</span>
          </div>
        </div>
      </div>
      <div class="task-info-line">出航时间：{{ taskInfo.eta }}</div>
    </div>
    <div class="task-route-list" v-show="!showPoint && !showAttitude && !showDeviation && !showSafetyJudge"
         v-loading="posesLoading" element-loading-background="rgba(255, 255, 255, 0.2)">
      <ElScrollbar :max-height="`${routeListMaxHeight.system}px`" always>
        <div
            v-for="(type, idx) in (props.task?.routeType || []).sort((a, b) => a - b)"
            :key="type"
            class="info-task-card"
            :class="{ 'is-active': isActive(type) }"
            @click="(e) => onTaskCardClick(e, type)"
        >
          <TaskCard
              :type="type"
              :class="'info-task-card__inner'"
              :stage="props.task?.taskStage!"
              :eta="taskInfo.typeInfo?.[idx].eta"
              :distance="taskInfo.typeInfo?.[idx].distance"
              :sog="taskInfo.typeInfo?.[idx].spd"
              :roll="taskInfo.typeInfo?.[idx].roll"
              :pitch="taskInfo.typeInfo?.[idx].pitch"
              deletable
              :loading="posesLoading"
              @show-point="handleShowPoint"
              @show-attitude="handleShowAttitude"
              @show-deviation="handleShowDeviation"
              @show-safety-judge="handleShowSafetyJudge"
              @delete-route="() => handleRouteDelete(type,'info')"
          />
        </div>
      </ElScrollbar>
    </div>
    <div class="task-route-list"
         v-show="!showPoint && !showAttitude && !showDeviation && !showSafetyJudge && props.customRoutePose.length"
         v-loading="customLoading" element-loading-background="rgba(255, 255, 255, 0.2)">
      <div
          v-for="(route, idx) in customRoutePoseArr"
          :key="`cus-${idx}`"
          class="info-task-card"
          :class="{ 'is-active': isActive(-1000 - idx) }"
          @click="(e) => onTaskCardClick(e, -1000 - idx)"
      >
        <TaskCard
            :stage="props.task?.taskStage!"
            :name="route.name"
            :type="-1"
            :eta="route.eta"
            :distance="route.distance"
            :sog="route.sog"
            :roll="route.roll"
            :pitch="route.pitch"
            :loading="posesLoading"
            deletable
            @show-point="() => handleShowCustomPoint(idx)"
            @show-attitude="() => handleShowCustomAttitude(idx)"
            @show-deviation="() => handleShowCustomDeviation(idx)"
            @delete-route="() => handleRouteDelete(idx,'diy')"
        />
      </div>
    </div>
    <div class="task-attitude" v-show="showAttitude">
      <div class="task-attitude-header">
        <ElIcon class="task-info-return" @click="() => toRouteList()">
          <ArrowLeftBold/>
        </ElIcon>
        <span>姿态预测曲线</span>
        <span style="margin-left: 20px;cursor: pointer" @click="startCompare">导航记录对比</span>
        <!-- 仅在单点姿态模式显示：八方向下拉（默认正北） -->
        <div v-if="flagForecast.active" style=" margin-left:auto;display:flex;align-items:center;gap:6px;">
          <span style="font-size:12px;color:#fff;">航向：</span>
          <ElSelect
              v-model="flagForecast.selected"
              size="small"
              style="width:100px;"
              :disabled="flagForecast.loading"
              @change="(v:any) => onFlagDirectionChange(v)"
          >
            <ElOption v-for="opt in DIR_OPTIONS" :key="opt.value" :label="opt.label" :value="opt.value"/>
          </ElSelect>
        </div>
      </div>
      <ElScrollbar :max-height="`${routeListMaxHeight.attitude}px`" always>
        <div ref="taskAttitudeDivRef" class="task-attitude-container"></div>
      </ElScrollbar>
    </div>
    <div class="task-attitude" v-show="showDeviation">
      <div class="task-attitude-header">
        <ElIcon class="task-info-return" @click="() => toRouteList()">
          <ArrowLeftBold/>
        </ElIcon>
        <span>累积横向位移</span>
      </div>
      <div ref="taskDeviationDivRef" class="task-deviation-container"></div>
    </div>
    <div class="task-attitude" v-show="showSafetyJudge">
      <div class="task-attitude-header">
        <ElIcon class="task-info-return" @click="() => toRouteList()">
          <ArrowLeftBold/>
        </ElIcon>
        <span>安全评估</span>
      </div>
      <SafetyJudge ref="safetyJudgeRef" :task-id="props.task?.id!"/>
    </div>
    <div class="task-point" v-show="showPoint">
      <div class="task-attitude-header">
        <ElIcon class="task-info-return" @click="() => toRouteList()">
          <ArrowLeftBold/>
        </ElIcon>
        <span v-if="chosenType !== -1">点位信息列表</span>
        <ElInput v-else v-model="editRegionRoute.name" size="small" style="width: 160px;"
                 placeholder="请输入自定义航线名称"/>
        <ElButton
            v-if="(props.task?.taskStage === TaskType.warStart) && editRegionRoute.id > -1"
            size="small"
            :loading="saveAsNewLoading"
            style="margin-left: auto;"
            @click="() => handleSavePoint(true)"
        >另存为
        </ElButton>
        <ElButton
            size="small"
            :loading="saveLoading"
            @click="() => handleSavePoint()"
            :style="{marginLeft: editRegionRoute.id === -1 ? 'auto' : '12px'}"
        >保存
        </ElButton>
      </div>
      <div v-if="chosenType === -1" class="task-region-route-form">
        <span>
          <span style="flex-shrink: 0;">航速：</span>
          <ElSelect
              v-model="editRegionRoute.sog"
              popper-class="custom-select-popper"
              placeholder="请选择航速"
              style="width: 80px;"
              size="small"
              @change="() => handleCustomSogChange()"
          >
            <ElOption
                v-for="option in customRouteSpdOptions"
                :key="option.value"
                :value="option.value"
                :label="option.label"
            />
          </ElSelect>
        </span>
        <span>
          <span>出航时间：</span>
          <ElDatePicker
              v-model="editRegionRoute.etd"
              type="datetime"
              placeholder="请选择出航时间"
              style="width: 160px;"
              size="small"
              @change="() => handleCustomEtdChange()"
              :clearable="false"
          />
        </span>
      </div>
      <ElTable
          ref="tableRef"
          class="way-point-table"
          border
          fit
          :data="tableData"
          :highlight-current-row="true"
          :header-cell-style="{ background: '#132e7a', color: '#ffffff' }"
          :height="pointTableHeight"
          @cell-dblclick="handleCellDblClick"
          @row-click="handleRowClick"
          @row-contextmenu="handleRowRightClick"
      >
        <ElTableColumn fixed label="序号" width="36">
          <template #default="scope">
            <span>{{ scope.$index + 1 }}{{ scope.row.isPilot ? '*' : '' }}</span>
          </template>
        </ElTableColumn>
     <ElTableColumn fixed prop="location" label="位置" width="164">
          <template #default="scope">
            <input
              v-if="scope.$index === editCell.row && scope.column.property === editCell.column"
              v-model="editCell.content"
              class="edit-input"
              :placeholder="'DD° MM.mm′ N/S,DDD° MM.mm′ E/W'"
              maxlength="40"
              @input.stop="handleLocationInput"
              @blur.stop="() => handleEditLocationBlur()"
            />
            <span v-else>{{ toLonLatString(scope.row.lon, scope.row.lat) }}</span>
          </template>
        </ElTableColumn>
        <ElTableColumn label="偏航角(°)" width="66">
          <template #default="scope">
            <span>{{ !scope.row.cog && scope.row.cog !== 0 ? '--' : scope.row.cog.toFixed(0) }}</span>
          </template>
        </ElTableColumn>
        <ElTableColumn label="距离(nm)" width="66">
          <template #default="scope">
            <span>{{ (scope.row.distance || scope.row.distance === 0) ? scope.row.distance.toFixed(2) : '--' }}</span>
          </template>
        </ElTableColumn>
        <ElTableColumn label="累积(nm)" width="66">
          <template #default="scope">
            <span>{{ (scope.row.totalDis || scope.row.totalDis === 0) ? scope.row.totalDis.toFixed(2) : '--' }}</span>
          </template>
        </ElTableColumn>
        <ElTableColumn label="到达时间" width="164">
          <template #default="scope">
            <span>{{ getCustomLocalTimeFormat(scope.row.ts * 1000, 'YYYY-MM-DD HH:mm') }}</span>
          </template>
        </ElTableColumn>
      </ElTable>
    </div>
    <div ref="wpMenuContent" class="wp-context-menu">
      <!-- <div ref="major" class="item" @click="setMajor(true)">设为Major</div>
      <div ref="nonMajor" class="item" @click="setMajor(false)">取消Major</div> -->
      <div
          class="item"
          :style="{ display: clickContextIdx === 0 ? 'none' : 'block' }"
          @click="() => pushRowUp()"
      >
        向上插入
      </div>
      <!-- <div ref="pushDown" class="item" @click="pushRowDown()">向下插入</div> -->
      <div
          class="item"
          :style="{
          display:
            tableData[clickContextIdx]?.isPilot || tableData[clickContextIdx]?.isSubPilot
              ? 'none'
              : 'block',
        }"
          @click="() => deleteRow()"
      >
        删除
      </div>
    </div>
    <!-- 地图旗标表单组件（独立 popup，使用 mapbox Popup 挂载到地图） -->
    <FormMarker
      ref="formMarkerRef"
      :area-points="areaPointsForFlag"
      :loading="flagForecast.loading"
      @confirm="handleFlagConfirm"
      @cancel="handleFlagCancel"
    />
  </ElDialog>

  <DataCompare v-model="compareVisible" :predicted="attitudePredictedForCompare"/>
</template>

<style lang="less" scoped>
.task-info-card {
  padding: 12px;
  background: linear-gradient(180deg, rgba(0, 39, 97, 0.8137) 0%, rgba(25, 36, 57, 0.8474) 100%);
  box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.0727);
  border-radius: 4px;
  font-weight: 400;
  font-size: 13px;
  color: #FFFFFF;
  line-height: 20px;

  .task-info-header {
    display: flex;
    align-items: center;
  }

  .task-info-return,
  .task-info-close {
    flex-shrink: 0;
    cursor: pointer;
  }

  .task-info-return {
    margin-right: 4px;
  }

  .task-info-close {
    margin-left: 4px;
  }

  .task-info-name {
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .task-info-line {
    margin-top: 8px;
    padding: 6px 8px;
    font-size: 12px;
    background: linear-gradient(53deg, rgba(129, 201, 255, 0.2404) 0%, rgba(120, 196, 255, 0.0762) 100%);
    border-radius: 4px 4px 4px 4px;
    border: 1px solid;
    opacity: 0.9346;
    border-image: linear-gradient(208deg, rgba(217.96235382556915, 240.45918345451355, 247.8204220533371, 0), rgba(178.97035092115402, 221.51232093572617, 237.7783665060997, 0.4698716700077057)) 1 1;
  }

  .task-info-stage {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 14px;
    font-weight: 700;

    :deep(.el-button--small) {
      padding: 2px 6px;
    }
  }

  .task-info-region {
    display: flex;
    align-items: flex-start;
  }

  .task-info-region-loc {
    display: flex;
    flex-direction: column;
    flex: 1;
  }

  .add-route-icon {
    width: 20px;
    height: 20px;
    fill: #fff;
    cursor: pointer;
  }

  .task-info-region {
    // ...existing styles...
    .task-action-btn {
      display: flex;
      align-items: center;
      cursor: pointer;
      padding: 3px 6px;
      border: 1px solid #1F93FF;
      border-radius: 4px;
      background: rgba(31, 147, 255, 0.10);
      transition: all .15s ease;
      user-select: none;

      &:not(:first-child) {
        margin-top: 8px;
      }

      &:hover {
        background: rgba(31, 147, 255, 0.18);
        box-shadow: inset 0 0 22px rgba(31,147,255,0.27);
      }

      &.is-active {
        background: linear-gradient(53deg, rgba(129, 201, 255, 0.40) 0%, rgba(120, 196, 255, 0.22) 100%);
        border-color: #FFFFFF;
        box-shadow: 0 0 0 1px #FFFFFF inset, 0 0 12px rgba(255,255,255,0.22);
      }

      .add-route-icon {
        margin-right: 6px;
      }
    }
  }
}

.task-route-list {
  margin-top: 10px;
  padding: 12px 0px 12px 10px;
  background: linear-gradient(180deg, rgba(0, 39, 97, 0.8137) 0%, rgba(25, 36, 57, 0.8474) 100%);
  box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.0727);
  border-radius: 4px;
}

.task-point,
.task-attitude {
  margin-top: 10px;
  background: linear-gradient(180deg, rgba(0, 39, 97, 0.89) 0%, rgba(25, 36, 57, 0.92) 100%);
  box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.07);
  border-radius: 4px;

  .task-attitude-header {
    font-weight: 500;
    font-size: 14px;
    background-image: linear-gradient(180deg, #FFFFFF 0%, #1F93FF 100%);
    color: transparent;
    background-clip: text;
    display: flex;
    align-items: center;

    .task-info-return {
      color: #fff;
      font-size: 12px;
      margin-right: 4px;
      cursor: pointer;
    }
  }
}

.task-attitude {
  .task-attitude-header {
    padding: 12px 12px 0 12px;
  }

  .task-attitude-container {
    height: 500px;
    width: 100%;
  }

  .task-deviation-container {
    height: 300px;
  }
}

.task-point {
  padding: 12px;

  .task-attitude-header {
    padding-bottom: 6px;
  }

  .edit-input,
  .edit-input:focus {
    background-color: rgba(197, 222, 245, 0);
    width: 100%;
    height: 20px;
    line-height: 20px;
    padding: 0;
    outline: none;
  }

  .edit-input {
    cursor: pointer;
    border: none;
  }

  .edit-input:focus {
    background-color: rgb(255, 255, 255);
    border: 1px solid #999;
    border-radius: 0;
  }
}

.task-region-route-form {
  margin-bottom: 6px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 12px;
  color: #fff;

  & > span {
    display: flex;
    align-items: center;
  }
}

.wp-context-menu {
  display: none;
  position: absolute;
  width: 60px;
  border: 1px solid #999;
  z-index: 3;
  background-color: #fff;
  border-bottom: none;
  box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);

  .item {
    height: 20px;
    line-height: 20px;
    width: 100%;
    text-align: center;
    font-size: 10px;
    cursor: pointer;
    border-bottom: 1px solid #999;
  }

  .item:hover {
    color: #ffffff;
    background-color: #3e9ffa;
    border-bottom: 1px solid #1890ff;
  }
}
</style>

<style lang="less">
.custom-dialog.task-info-dialog.el-dialog {
  margin-left: 20px;
  border: none;
  box-shadow: none;
  --el-dialog-bg-color: transparent;
  --el-dialog-margin-top: 95px;
  --el-dialog-padding-primary: 0;

  .el-dialog__body {
    padding: 0 0;
  }

  .dialog-footer {
    padding: 6px 76px 0 60px;
  }

  .el-dialog__header {
    display: none;
  }

  .task-attitude-header {
    .el-button {
      border-radius: 2px;
      background-color: transparent;
      color: #fff;
      box-shadow: inset 0px 0px 22px 0px rgba(31, 147, 255, 0.27);
      border-radius: 2px;
      border: 1px solid #1F93FF;

      &:hover {
        color: #1F93FF;
      }
    }
  }
}

.way-point-table {
  --el-table-bg-color: transparent;
  --el-table-tr-bg-color: #093d6d;
  --el-table-row-hover-bg-color: #0c4b86;
  --el-table-border-color: rgba(255, 255, 255, 0.17);
  --el-table-text-color: rgba(255, 255, 255, 0.80);

  tr td,
  tr th {
    padding: 0 !important;
  }

  .cell {
    padding: 0 0 0 5px !important;
    height: 20px;
    line-height: 20px;
    font-size: 12px;
    cursor: pointer;
    font-weight: 400;
    display: flex;
    align-items: center;
  }

  thead .cell {
    font-weight: 700;
  }

  .current-row td {
    background: #0e589c !important;
  }
}

/* 外层卡片容器（包裹 TaskCard 的 div），用于 hover / active */
.info-task-card {
  /* 让整个卡片区域都可点（但按钮处不会选中，见点击逻辑） */
  cursor: pointer;
  border-radius: 4px;
  margin-bottom: 10px;

  &:last-of-type {
    margin-bottom: 0;
  }


  /* 选中（active）时：再亮一点 */
  &.is-active .task-route-item {
    background: linear-gradient(53deg, rgba(129, 201, 255, 0.48) 0%, rgba(120, 196, 255, 0.28) 100%) !important;
    border-color: #fff !important;
    border-image: none !important;
    box-shadow: 0 0 0 1px #fff inset, 0 0 16px rgba(255, 255, 255, 0.28);
  }
}

/* 可选：让内部按钮在 hover/active 时保持本来的样式层级 */
.info-task-card .task-route-item-btns {
  position: relative;
  z-index: 1;
}
</style>
